#include <windows.h>
#include <commctrl.h>

#include "gui/gui.h"
#include "gui/GForm.h"
#include "gui/GView.h"
#include "platform.h"


#pragma comment(lib, "comctl32")

LRESULT  CALLBACK  windowCallBack(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);


//namespace platform{

HINSTANCE g_hInstance = NULL;
TCHAR szAppName[] = TEXT("CoolEditor");

int init(int argc, char* argv[])
{
    HINSTANCE hInstance = ::GetModuleHandle(NULL);

    INITCOMMONCONTROLSEX icce;
    icce.dwSize = sizeof(icce);
    icce.dwICC = ICC_TAB_CLASSES;
    ::InitCommonControlsEx(&icce);

    WNDCLASSEX   wndclassex = {0};
    wndclassex.cbSize        = sizeof(WNDCLASSEX);
    wndclassex.style         = CS_HREDRAW | CS_VREDRAW;
    wndclassex.lpfnWndProc   = windowCallBack;
    wndclassex.cbClsExtra    = 0;
    wndclassex.cbWndExtra    = 0;
    wndclassex.hInstance     = hInstance;
    wndclassex.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
    wndclassex.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wndclassex.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    wndclassex.lpszMenuName  = NULL;
    wndclassex.lpszClassName = szAppName;
    wndclassex.hIconSm       = wndclassex.hIcon;
    
    if (!::RegisterClassEx (&wndclassex))
    {
        ::MessageBox(NULL, GTEXT("RegisterClassEx failed!"), GTEXT("GForm Editor"), MB_ICONERROR);
        return -1;
    }
    g_hInstance = hInstance;

    return 0;
}

void release()
{
    
}

GHForm createWindow(WindowType type, GForm* pForm)
{
    GHForm handle = 0;
    HINSTANCE hInstance = g_hInstance;
    if (pForm == NULL)
        return 0;

    GForm* pParent = pForm->getParent();
    HWND hWndParent = pParent == 0 ? 0 : (HWND)pParent->getHandle();
    HWND   hwnd = 0;

    switch (type)
    {
    case WINDOW:
        {
           hwnd  = ::CreateWindowEx (WS_EX_OVERLAPPEDWINDOW, 
                szAppName, 
                TEXT ("WindowTitle"),
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
                hWndParent, 
                NULL, 
                hInstance,
                pForm); 
            
        }
        break;
    case VIEW:
        {
            hwnd  = ::CreateWindowEx ( 0, 
                szAppName,  TEXT (""),
                WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                0, 25, 100, 100, //CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,  
                hWndParent, 
                NULL, 
                hInstance,
                pForm);
            ::DragAcceptFiles(hwnd, true);
        }
        break;
    case TABBAR:
        {
            hwnd  = ::CreateWindowEx ( 0,
                szAppName,  TEXT("Tab"),
                WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
                TCS_FOCUSNEVER | TCS_TOOLTIPS,
                0, 0, 100, 30,
                hWndParent,
                0, //reinterpret_cast<HMENU>(IDM_TABWIN),
                hInstance,
	            pForm);
        }
        break;
    default:
        break;
    }
    handle = (GHForm)hwnd;
	if (handle)
	{
		// send a async message to init something in createEvent when entering message loop...
		::PostMessage(hwnd, WM_INITWINDOW, 0 , 0); 
	}
    return handle;
}

void destroyWindow(GHForm handle)
{
    ::DestroyWindow( (HWND)handle); 
}

void showWindow(GHForm handle, int cmd)
{
    HWND hwnd = (HWND)handle;
    int show = cmd ? SW_SHOW : SW_HIDE;
    ::ShowWindow (hwnd, show);
    ::UpdateWindow (hwnd);
}

void closeWindow(GHForm handle)
{
    ::PostMessage((HWND)handle, WM_CLOSE, 0, 0);
}

int  messageLoop()
{
    int ret = 0;
    {
        MSG          msg;
        while (::GetMessage (&msg, NULL, 0, 0))
        {
            ::TranslateMessage (&msg);
            ::DispatchMessage (&msg);
        }
        ret = msg.wParam;
    }
    
    return ret;
}

int  sendToForm(const GForm* pRecv, const GMessage& msg)
{
    return ::PostMessage((HWND)pRecv->getHandle(), msg.message, msg.wParam, msg.lParam);
}

GMenu::GMenu(GHMenu hMenu /* = 0 */)
:m_hMenu(hMenu) 
,m_szTitle(GTEXT("Untitle"))
{
	if (m_hMenu == 0)
		m_hMenu = ::CreateMenu();
}

bool GMenu::addItem(uint itemID, const GString& text)
{
	return ::AppendMenu((HMENU)m_hMenu, MF_ENABLED | MF_POPUP, itemID , text.c_str()) ?true :false;
}
bool GMenu::addSeparator()
{
    return ::AppendMenu((HMENU)m_hMenu, MF_ENABLED | MF_SEPARATOR, 0, 0) ? true :false;
}
bool GMenu::insertItem(uint index, uint itemID, const GString& text)
{
	return ::InsertMenu((HMENU)m_hMenu, index, MF_ENABLED | MF_POPUP | MF_BYPOSITION , itemID , text.c_str()) ?true :false;
}
bool GMenu::addMenu(const GMenu& menu)
{
	return ::AppendMenu((HMENU)m_hMenu, MF_ENABLED | MF_POPUP, (UINT)menu.m_hMenu , menu.getTitle().c_str()) ?true :false;
}
bool GMenu::insertMenu(uint index, const GMenu& menu)
{
	return ::InsertMenu((HMENU)m_hMenu, index, MF_ENABLED | MF_POPUP | MF_BYPOSITION , 
        (UINT)menu.m_hMenu , menu.getTitle().c_str()) ?true :false;
}

GMenu  GMenu::getSubHandle(int index /* = 0 */)
{
    return ::GetSubMenu((HMENU)m_hMenu, index);
}

bool GMenu::setCheck(int itemID, bool check /* = true */)
{
    UINT state = MF_BYCOMMAND | (check ? MF_CHECKED : MF_UNCHECKED);
    return ::CheckMenuItem((HMENU)m_hMenu, itemID,  state) ? true : false;  
}
bool GMenu::destroy( GMenu& menu)
{
    bool ret = ::DestroyMenu((HMENU)menu.m_hMenu) ? true : false;
    menu.m_hMenu = 0;
    menu.m_szTitle = GTEXT("");
    return ret;
}
bool  setWindowMenu(GForm* pWindow, GMenu menu)
{
	return ::SetMenu((HWND)pWindow->getHandle(), (HMENU)menu.getHandle()) ? true : false;
}
//////////////////////////////////////////////////////////////////////////
//Caret
GCaret::GCaret(GForm* pForm, int w /* = 1 */, int h /* = 16 */)
:m_szCaret(w,h)
,m_pForm(pForm)
,m_bIsShown(false)
{
    create(GSize(w,h));
}
bool  GCaret::create(const GSize& size)
{
    HWND hwnd = (HWND)m_pForm->getHandle();

    BOOL b = ::CreateCaret(
        hwnd, 0,
        size.w, 
        size.h);
    m_szCaret = size;
    m_bIsShown = false;
    return b ? true : false;
}
void  GCaret::show()  
{
    if (!m_bIsShown)
    {
        ::ShowCaret((HWND)m_pForm->getHandle());
        m_bIsShown = true;
    }
    
}

void  GCaret::hide()  
{
    if (m_bIsShown)
    {
        ::HideCaret((HWND)m_pForm->getHandle());
        m_bIsShown = false;
    }
    
}

void  GCaret::setPos(const GPoint& point)
{
//     if (m_ptCaret == point)
//         return;
    m_ptCaret = point;
    ::SetCaretPos(point.x, point.y);
    show();
}


//////////////////////////////////////////////////////////////////////////
void  getShortCut(ShortCut& cs)
{
    if (::GetAsyncKeyState(VK_CONTROL) & 0x8000)
        cs.setCtrl();
    if (::GetKeyState(VK_MENU ) & 0x8000)
        cs.setAlt();
    if (::GetKeyState(VK_SHIFT) & 0x8000)
        cs.setShift();
}

void  windowClientRect(const GForm* w, GRect& rc)
{
	::GetClientRect((HWND)w->getHandle(), (RECT*)&rc);
}

bool  setWindowRect( GForm* w, const GRect& rc)
{
	return ::MoveWindow((HWND)w->getHandle(), rc.left, rc.top, rc.width(), rc.height(), TRUE)? true:false;
}

void setWindowFocus(GForm* pForm, bool focused)
{
	if (focused)
		::SetFocus((HWND)pForm->getHandle());
    
}

void setWindowCursor(GForm* pForm, GCursor::CursorShape cursor)
{
	UINT c = 32512;
	switch (cursor)
	{
	case GCursor::Default:
	case GCursor::Arrow: c = 32512; break;
	case GCursor::Text:  c = 32513; break;
	case GCursor::Up:    c = 32516; break;
	case GCursor::Wait:  c = 32514; break;
	case GCursor::Horiz: c = 32644; break;
	case GCursor::Vert:  c = 32645; break;
	//case GCursor::ReverseArrow:  c = ::GetReverseArrowCursor(); break;
	case GCursor::Hand:  c = 32649; break;
	}
	HCURSOR hc = ::SetCursor(::LoadCursor(NULL,MAKEINTRESOURCE(c)));
}

void  repaintWindow(const GForm* pForm, const GRect& rc)
{
	RECT* pRect = 0;
	RECT r;
	HWND hWnd = (HWND)pForm->getHandle();
	if (rc.isValid())
		pRect = (RECT*)&rc;
	else {
		::GetClientRect(hWnd, &r);
		pRect = &r;
	}
	::InvalidateRect(hWnd, pRect, true);
}

//////////////////////////////////////////////////////////////////////////
// Scroll Window
void  setGViewScroll(GView* pView, int type)
{
    static int count = 0;
    dprint("setGViewScroll:%d times\n", count++);
    HWND hWnd = (HWND)pView->getHandle();
    int pos, counts;
    pView->getViewScroll((GView::ScollBar)type, &pos, &counts);
    
    SCROLLINFO sci = { sizeof(SCROLLINFO), 0, 0, 0, 0, 0, 0 };
    sci.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
    sci.nMin = 0;
    sci.nMax = counts;
    sci.nPage = pView->getLinesOnScreen((GView::ScollBar)type); 
    sci.nPos = pos;
    sci.nTrackPos = 1;
	::SetScrollInfo(hWnd, type, &sci, TRUE);

}

void scrollViewTo(GView* pView, int type,  int pos)
{
    HWND hwnd = (HWND)pView->getHandle();
    if (pos != GetScrollPos(hwnd, type)) {
        SetScrollPos(hwnd, type, pos, TRUE);
        InvalidateRect(hwnd, NULL, TRUE);
    }
}

//////////////////////////////////////////////////////////////////////////

#ifndef WM_MOUSEWHEEL
#define   WM_MOUSEWHEEL       0x020A 
#endif
long messageDispatch(GForm* pForm, const GMessage& msg) // friend to GForm 
{
    
    static bool mouseEnter = false;
    HWND hwnd = (HWND)pForm->getHandle();

    switch (msg.message)
    {
	case WM_CREATE:
		dprint("WM_CREATE:hwnd=%d\n", pForm->getHandle());
		return 0;
    case WM_INITWINDOW:
        pForm->createEvent();
        return 0;
    case WM_CLOSE:
        pForm->closeEvent();
        return 0;
    case WM_NCDESTROY:
        pForm->destroyEvent();
        return 0;
    case WM_COMMAND:
        pForm->itemEvent(msg.wParam, msg.lParam);
        dprint("COMMAND:%d,%d\n", msg.wParam, msg.lParam);
        return 0;
    case WM_NOTIFY:
        dprint("NOTIFY:%d,%d\n", msg.wParam, msg.lParam);
        return 0;
    case WM_SYSKEYDOWN:
    case WM_KEYDOWN:
        {
            ShortCut  sc;
            sc.setChar((char)msg.wParam);
            getShortCut(sc);
            if (sc.isValid()) { 
                pForm->keysDownEvent(sc);
			//	dprint("SHORTCUT:wParam=%d,ch=%c,%d\n", msg.wParam, msg.wParam, msg.lParam);
            }
        }
        return -1;
    case WM_SYSKEYUP:
    case WM_KEYUP:
        {
            ShortCut  sc;
            sc.setChar((char)msg.wParam);
            getShortCut(sc);
            if (sc.isValid()) { 
                pForm->keysUpEvent(sc);
            }
        }
        return 0;
    case WM_CHAR:
		{
			ShortCut  sc;
			sc.setChar((char)msg.wParam);
			getShortCut(sc);
			if (sc.hasAlt() || sc.hasCtrl())
				break;
            
            //if (::IsWindowUnicode(hwnd)) return 0;
            pForm->charPressEvent((wchar_t)msg.wParam);
            dprint("CHAR:wParam=%d,ch=%c,UniWindow=%d\n", msg.wParam, msg.wParam, ::IsWindowUnicode(hwnd));
		}
        return 0;
    case WM_UNICHAR:
        {
            if (!::IsWindowUnicode((HWND)pForm->getHandle()))
                break;
            dprint("WM_UNICHAR:wParam=%d,ch=%c,%d\n", msg.wParam, msg.wParam, msg.lParam);
            break;
        }
    case WM_IME_CHAR: //if not handle WM_IME_CHAR(break and call DefWindowProc), WM_CHAR will response
        {
            wchar_t ch = (wchar_t)msg.wParam;
            if (!::IsWindowUnicode(hwnd)) //wParam is the mbc encoding char
            {
                char Buffer[3] ={0};
                Buffer[0] = ch >> 8;
                Buffer[1] = (char)ch;
                Buffer[2] = 0;
                ::MultiByteToWideChar(0, 0, Buffer,2, &ch, 1);
            }
            
            pForm->charPressEvent(ch);
            dprint("WM_IME_CHAR:wParam=%x,ch=%c,UniWindow=%d\n", ch, msg.wParam, ::IsWindowUnicode((HWND)pForm->getHandle()));
            return 0; // it should return so as not to call DefWindowProc, otherwise WM_CHAR will response
        }
    case WM_MOUSEMOVE:
        {
            TRACKMOUSEEVENT tme;
            tme.cbSize  =   sizeof (tme);
            tme.hwndTrack  =  (HWND)pForm->getHandle();
            tme.dwFlags  =  TME_LEAVE  |  TME_HOVER;
            tme.dwHoverTime  =   1 ;
            ::_TrackMouseEvent( & tme); 

            GMouseEvent event;
            uchar mask = (uchar)msg.wParam;
            event.setButtons((uchar)msg.wParam);
            event.m_ptPos = GPoint(LOWORD(msg.lParam), HIWORD(msg.lParam));
            if (mouseEnter == false)
            {
                dprint("WM_MOUSEENTER:\n");
                mouseEnter = true;
                pForm->enterEvent(event);
				
                break;
            }
            pForm->mouseMoveEvent(event);
            break;
        }
    case WM_MOUSELEAVE:
        {
            dprint("WM_MOUSELEAVE:\n");
            mouseEnter = false;
            GMouseEvent event;
            uchar mask = (uchar)msg.wParam;
            event.setButtons(mask);
            event.m_ptPos = GPoint(LOWORD(msg.lParam), HIWORD(msg.lParam));
            pForm->leaveEvent(event);
			//::ReleaseCapture();
        }
        break;
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
        {
            GMouseEvent event;
            uchar mask = (uchar)msg.wParam;
            event.setButtons((uchar)msg.wParam);
            event.m_ptPos = GPoint(LOWORD(msg.lParam), HIWORD(msg.lParam));
            dprint("WM_MOUSEEVENT:HWND=%d,wParam=%x,Pos=(%d,%d)\n",pForm->getHandle(), msg.wParam, event.m_ptPos.x, event.m_ptPos.y);

            void (GForm::* pf)(GMouseEvent& ) = 0;
            switch (msg.message)
            {
            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                SetCapture(hwnd);
                pf = &GForm::mousePressEvent;
                break;
            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                ReleaseCapture();
                pf = &GForm::mouseReleaseEvent;
                break;
            case WM_LBUTTONDBLCLK:
            case WM_RBUTTONDBLCLK:
                pf = &GForm::mouseDoubleClickEvent;
                break;
            case WM_MOUSEMOVE:
                pf = &GForm::mouseMoveEvent;
                break;
            }
            (pForm->*pf)(event);
        }
        return 0;
    case WM_SIZING:
        dprint("ING-");
    case WM_SIZE:
        {
            GSizeEvent  event;
            event.m_Size = GSize(LOWORD(msg.lParam), HIWORD(msg.lParam));
            event.m_type = (GSizeEvent::SizeType)msg.wParam;
            dprint("WM_SIZE:wParam=%d,Size=(%d,%d)\n", msg.wParam, event.m_Size.w, event.m_Size.h);
            pForm->sizeEvent(event);
        }
        break;
    case WM_MOUSEWHEEL:
        {
            GWheelEvent event;
            event.m_Flag = LOWORD(msg.wParam);
            event.m_Direct = (short)HIWORD(msg.wParam);
            event.m_ptPos = GPoint(LOWORD(msg.lParam), HIWORD(msg.lParam));
            dprint("WM_MOUSEWHEEL:hWnd=%d, delta=%d,Point=(%d,%d)\n", pForm->getHandle(), event.m_Direct, event.m_ptPos.x, event.m_ptPos.y);
            pForm->wheelEvent(event);
        }
        break;
        
    case WM_PAINT:
        {
            HDC hdc;
            HWND hwnd = (HWND)pForm->getHandle();
            PAINTSTRUCT ps;
            hdc = ::BeginPaint (hwnd, &ps);
            GPaintEvent event;
            event.m_rcPaint.adjust(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom);
            pForm->paintEvent(event);
            //TextOut (hdc, 0, 0, "A Window!", 27);
            ::EndPaint (hwnd, &ps);
        }
        return -1;
	case WM_ERASEBKGND:
		return 1;

    case WM_KILLFOCUS:
        pForm->setFocus(false);
        break;
	case WM_VSCROLL:
	case WM_HSCROLL:
		{
			GView* pView = static_cast<GView*>(pForm);
			if (pView  == 0)
				break;
			int curpos, counts;
            GView::ScollBar bar = GView::VSCROLL;
            if (msg.message == WM_HSCROLL)
                bar = GView::HSCROLL;
            
            pView->getViewScroll(bar, &curpos, &counts);
			int pos = curpos;
			switch (LOWORD(msg.wParam))
			{
			case SB_THUMBTRACK:
			case SB_THUMBPOSITION:
				pos = HIWORD(msg.wParam);
				break;
			case SB_LINEUP:
				pos = curpos-1;
				break;
			case SB_LINEDOWN:
				pos = curpos+1;
				break;
			case SB_TOP:
				pos = 0;
				break;
			case SB_BOTTOM:
				pos = counts;
				break;
			case SB_PAGEUP:
				pos = curpos - pView->getLinesOnScreen(bar);
				break;
			case SB_PAGEDOWN:
				pos = curpos + pView->getLinesOnScreen(bar);
				break;
			}
            GScrollEvent event;
            event.oldPos = curpos;
            event.pos = pos;
            event.type = bar;
            event.counts = counts;
            pForm->scrollEvent(event);
		}
		return 0;
    case WM_DROPFILES:
        {
            HDROP hdrop = reinterpret_cast<HDROP>(msg.wParam);
            int filesDropped = ::DragQueryFile(hdrop, 0xffffffff, NULL, 0);
            gchar pathDropped[MAX_PATH];
            GDropEvent event;
            for (int i = 0; i < filesDropped; ++i) {
                ::DragQueryFile(hdrop, i, pathDropped, MAX_PATH);
                event.push_back(pathDropped);
            }
            ::DragFinish(hdrop);
            pForm->dropEvent(event);
            ::SetForegroundWindow((HWND)pForm->getHandle());
            break;
        }
        break;
    default:
        return pForm->messageEvent(msg);
	}
    return -1; // -1 means default operation
}

//}

//////////////////////////////////////////////////////////////////////////

LRESULT CALLBACK windowCallBack (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    long ret = 0;
	GForm* pForm = 0; 
    
    if (GForm::mainForm() && hwnd == GForm::mainForm()->getHandle() && message == WM_DESTROY ) //quit condition
    {
        ::PostQuitMessage (0);
        return 0;
    }
	//Associate hwnd with GForm*
	if (message == WM_CREATE && lParam != 0)
	{
		GForm* p = 0;
		LPCREATESTRUCT cs = reinterpret_cast<LPCREATESTRUCT>(lParam);
		p = reinterpret_cast<GForm*>(cs->lpCreateParams);
		p->setHandle(hwnd);
	}

	pForm = GForm::getForm(hwnd);
    if (pForm != NULL)
    {
        ret = messageDispatch(pForm, GMessage(message, wParam, lParam));
        if (ret != -1) // if ret == -1 GForm need to call DefWindowProc
            return ret; 
    }
    
    return  ::DefWindowProc (hwnd, message, wParam, lParam);
}
